#pragma once
#ifdef __ANDROID__
#include <jni.h>
#include <android/log.h>
#include <unistd.h>// get pid/uid
#include <vector>
#include <string>

class AUtil {
public:
    static jobjectArray createJStringArray(JNIEnv *env, const std::vector<std::string>& strArr) {
        jclass stringClass = env->FindClass("java/lang/String");
        if (stringClass == nullptr)// cannot find the class
            return nullptr;

        jobjectArray stringArray = env->NewObjectArray(static_cast<jsize>(strArr.size()), stringClass, nullptr);
        for (size_t i = 0; i < strArr.size(); ++i) {
            jstring stringElement = env->NewStringUTF(strArr[i].c_str());
            env->SetObjectArrayElement(stringArray, static_cast<jsize>(i), stringElement);
            env->DeleteLocalRef(stringElement);
        }

        return stringArray;
    }

#define Get_IntArray_Ptr(jObject, fieldName) \
    AUtil::parseArray<int>(env, jObject, fieldName);
#define Get_FloatArray_Ptr(jObject, fieldName) \
    AUtil::parseArray<float>(env, jObject, fieldName);

#define RELEASE_ARRAY(...) \
    [&] { \
        auto func = [=](auto&&... args) { \
            (AUtil::releaseArray(env, args), ...); \
        }; \
        func(__VA_ARGS__); \
    }();

    template<typename E>
    static auto parseArray(JNIEnv *env, jobject jObject, const char* fieldName) {
        jclass objClass = env->GetObjectClass(jObject);
        if constexpr (std::is_same_v<E, int>) {
            jfieldID field = env->GetFieldID(objClass, fieldName, "[I");
            auto intArr = (jintArray) env->GetObjectField(jObject, field);
            auto arrayPtr = env->GetIntArrayElements(intArr, nullptr);
            return MakePair(arrayPtr, intArr);
        } else if constexpr (std::is_same_v<E, float>) {
            jfieldID field = env->GetFieldID(objClass, fieldName, "[F");
            auto floatArr = (jfloatArray) env->GetObjectField(jObject, field);
            auto arrayPtr = env->GetFloatArrayElements(floatArr, nullptr);
            return MakePair(arrayPtr, floatArr);
        } else
            static_assert(false, "Unsupported type: only jint or jfloat are allowed");
    }

#define Get_Int2DArray_Ptr(jObject, fieldName) \
    AUtil::parse2DArray<int>(env, jObject, fieldName);
#define Get_Float2DArray_Ptr(jObject, fieldName) \
    AUtil::parse2DArray<float>(env, jObject, fieldName);

#define RELEASE_2DARRAY(arrayPair) \
    AUtil::release2DArray(env, arrayPair)

    template<typename E>
    static auto parse2DArray(JNIEnv *env, jobject jObject, const char* fieldName) {
        jclass objClass = env->GetObjectClass(jObject);
        if constexpr (std::is_same_v<E, int>) {
            jfieldID field = env->GetFieldID(objClass, fieldName, "[[I");
            auto array2D = (jobjectArray)env->GetObjectField(jObject, field);
            jsize outerLength = env->GetArrayLength(array2D);
            std::vector<int*> pointers(outerLength);
            for (jsize i = 0; i < outerLength; i++) {
                auto innerArray = (jintArray)env->GetObjectArrayElement(array2D, i);
                pointers[i] = env->GetIntArrayElements(innerArray, nullptr);
            }
            return MakePair(pointers, array2D);
        } else if constexpr (std::is_same_v<E, float>) {
            jfieldID field = env->GetFieldID(objClass, fieldName, "[[F");
            auto array2D = (jobjectArray)env->GetObjectField(jObject, field);
            jsize outerLength = env->GetArrayLength(array2D);
            std::vector<float*> pointers(outerLength);
            for (jsize i = 0; i < outerLength; i++) {
                auto innerArray = (jfloatArray)env->GetObjectArrayElement(array2D, i);
                pointers[i] = env->GetFloatArrayElements(innerArray, nullptr);
            }
            return MakePair(pointers, array2D);
        } else
            static_assert(false, "Unsupported type: only jint or jfloat are allowed");
    }

    template<typename E, typename E_ARRAY>
    static void releaseArray(JNIEnv *env, const Pair<E*, E_ARRAY>& arrayPair) {
        if constexpr (std::is_same_v<E, jint>)
            env->ReleaseIntArrayElements(arrayPair.value, arrayPair.key, 0);
        else if constexpr (std::is_same_v<E, jfloat>)
            env->ReleaseFloatArrayElements(arrayPair.value, arrayPair.key, 0);
        else
            static_assert(false, "Unsupported type: only jint or jfloat are allowed");
        env->DeleteLocalRef(arrayPair.value);
    }

    template<typename E>
    static void release2DArray(JNIEnv *env, const Pair<std::vector<E*>, jobjectArray>& arrayPair) {
        auto [pointers, array2D] = arrayPair;
        jsize outerLength = env->GetArrayLength(array2D);
        if constexpr (std::is_same_v<E, int>) {
            for (jsize i = 0; i < outerLength; i++) {
                auto innerArray = (jintArray)env->GetObjectArrayElement(array2D, i);
                env->ReleaseIntArrayElements(innerArray, static_cast<int*>(pointers[i]), 0);
                env->DeleteLocalRef(innerArray);
            }
        } else if constexpr (std::is_same_v<E, float>) {
            for (jsize i = 0; i < outerLength; i++) {
                auto innerArray = (jfloatArray)env->GetObjectArrayElement(array2D, i);
                env->ReleaseFloatArrayElements(innerArray, static_cast<float*>(pointers[i]), 0);
                env->DeleteLocalRef(innerArray);
            }
        }
        env->DeleteLocalRef(array2D);
    }

#define Get_IntValue(jObject, fieldName) AUtil::parseValue<int>(env, jObject, fieldName)
#define Get_FloatValue(jObject, fieldName) AUtil::parseValue<float>(env, jObject, fieldName)

    template<typename E>
    static auto parseValue(JNIEnv *env, jobject jObject, const char *fieldName) {
        jclass objClass = env->GetObjectClass(jObject);
        if constexpr (std::is_same_v<E, int>) {
            jfieldID field = env->GetFieldID(objClass, fieldName, "I");
            return env->GetIntField(jObject, field);
        } else if constexpr (std::is_same_v<E, float>) {
            jfieldID field = env->GetFieldID(objClass, fieldName, "F");
            return env->GetFloatField(jObject, field);
        } else
            static_assert(false, "Unsupported type: only jint or jfloat are allowed");
    }
};
#endif